fix: audit batch — #133 #129#138
Merged
Merged
Conversation
The HTTP-flavored validateProjectDir in routes.ts had its core logic duplicated as an inlined copy inside tests/unit/audit-regressions.test.ts so the test could exercise it without an HTTP response object. The comment in the test acknowledged the drift risk on a security-sensitive path-containment boundary. Extract the pure validation to src/server/validate-project-dir.ts returning a discriminated result. routes.ts wraps it with the existing HTTP response surface; the regression test now imports the real function instead of a copy.
…loses #130) forceRepaint() pokes ghostty-web private fields (renderer, wasmTerm, viewportY) because no stable upstream API exists. A future bundle update that renames or restructures those fields would silently turn the call into a no-op, leaving stale frames after attach with no signal. - New public/terminal-repaint.ts exports hasGhosttyRepaintHook(term) feature-detecting the expected shape (pure, no side-effects). - forceRepaint() now probes via the helper and emits a single console.warn on shape drift before bailing — silent degradation becomes loud and diagnosable. - tests/unit/terminal-repaint.test.ts covers the positive case plus every drift mode (missing renderer, renamed field, non-function render, missing wasmTerm, missing viewportY, viewportY === 0).
Animated TUIs (spinners, htop, watch, claude's pulse spinner) never let the broker byte rate drop below QUIESCE_BYTE_THRESHOLD, so the settle loop in the prefill path always waited the full QUIESCE_TIMEOUT_MS (800ms) before snapshotting. The snapshot was taken mid-redraw and the live stream then had to overwrite the garbled frame — visible UX artifact lasting 150–800ms on attach. - Add QUIESCE_ANIMATED_CAP_MS = 200. When byte rate stays above the threshold through the entire window, snapshot at 200ms instead of waiting out the full timeout. Cuts the worst-case mid-redraw prefill window 4x. - Common case (apps that actually quiesce) is unchanged — they exit via the 'quiet' branch within ~100ms, well before the cap. - Extract the decision into a pure quiescenceDecision() function so the loop's three (now four) exit conditions are testable in isolation without a real broker. The setTimeout(16) wait and resize side-effects stay at the call site. - Add log.debug for animated_cap / timeout exits so operators can spot always-busy sessions in traces.
README: 302 → 155 lines.
- Lead with what it does + why (one paragraph), not marketing tagline.
- Primary install is the no-deps curl|bash; bunx/npx hidden in <details>.
- Drop tailscale 'optional' hedging — it's how you actually use this.
- Promote security setup ('Secure It' with JWT) above features so anyone
deploying on a tailnet sees it before scrolling past the demos.
- Collapse the Features grab-bag (Session Management / Desktop / Mobile /
Multi-Machine / Other / Remote Access / Security all flat-bulleted) into
one terse 'What It Does' list, ~8 cross-cutting bullets.
- Drop Contributing / Testing / Building / Asset Pipeline / PR Conventions /
migrate-plan — all moved to CONTRIBUTING.md.
- Drop 'Community & Support' boilerplate, garbled emoji, 'star the repo'
growth-hack copy.
- Cut from 4 screenshots to 2 (one desktop, one mobile).
- Keep wolf ASCII art + architecture diagram + config example.
CONTRIBUTING.md: new. Holds dev setup, testing, asset pipeline, build,
PR conventions, and the migrate-plan utility.
Verified empirically before writing: both image refs exist, all 7 CLI
commands exist in src/cli/index.ts, all 4 JWT env vars exist in
src/auth.ts, both doc links resolve.
…aming Tailscale already gates network reachability — for the common solo-user case that's enough. JWT is now framed as opt-in (shared tailnets, defense-in-depth), positioned after Architecture rather than between First Run and What It Does.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two small fixes from the open audit issues. Each is a separate commit with its own regression test. #130 and #131 left open — see below.
Closes
L16: audit-regressions.test.ts inlines a copy of validateProjectDir #133
refactor: extract validateProjectDir to pure moduleThe HTTP-flavored
validateProjectDirinroutes.tshad its core logicduplicated inside
tests/unit/audit-regressions.test.tsso the test couldexercise it without an HTTP response object — drift risk on a
security-sensitive path-containment boundary. Extracted to
src/server/validate-project-dir.tsreturning a discriminated result;routes.tswraps it; regression test now imports the real function.M5: quiescence loop produces stale snapshot for animated TUIs #129
fix(ws): cap quiescence wait at 200ms for animated TUIsAlways-busy sessions (spinners, htop,
watch, claude's pulse spinner)never drop below
QUIESCE_BYTE_THRESHOLD, so the prefill settle loopalways waited the full 800ms before snapshotting — capturing a
mid-redraw frame the live stream then had to overwrite. New
QUIESCE_ANIMATED_CAP_MS = 200short-circuits that wait. Common case(apps that quiesce) is unchanged. Decision extracted to pure
quiescenceDecision()for unit testing.Reverted in this branch
a warn-once shim around the existing
try { ... } catch {}. Theater:a console.warn in a mobile PWA fixes nothing. Reverted in 13cd446.
Real risk surface: ghostty-web is pinned to 0.4.0, no runtime
auto-update; only a deliberate dep bump can break the internals. Right
fix is either a CI test against the bundle or a comment at the
version-bump site, not defensive code in the hot path. See updated
issue comment.
Left open
WRITER_QUEUE_CAPACITYbackpressure — issue's user-impactclaim ("slow clients lose keyboard input + resize") is wrong:
Frame::InputBinarycallssession.write_stdindirectly, andFrame::ControlRequestrunsrouter.handlesynchronously (resize isapplied before its response queues). Only control-response delivery
sits behind a full queue, and
SubscriptionDroppedrecovery exists.Needs issue rewrite before deciding between hardening and wontfix.
Verification
bun test— 1483 pass / 0 fail (after revert)bunx tsc --noEmit— 32 errors, all pre-existing onmain, zero newscripts/bundle-app.tsregenerates cleanOut of scope (closed during triage as stale)
--append-system-promptsilent fallback: removed in 232dc07public/app.ts:348-349